﻿using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Text;
using Cloak.Work;

namespace Cloak.Mvp
{
	/// <summary>
	/// A context in which views, presenters, and domain model elements are composed
	/// </summary>
	public sealed class MvpContext : IMvpContext, IDisposable
	{
		private readonly IPresenterContext _presenterContext;
		private readonly IViewPresenterIndex _declaredViewPresenterIndex;
		private readonly IWorkContext _workContext;
		private readonly object _presentationSync = new object();
		private bool _disposed;

		/// <summary>
		/// Initializes a context with the specified view/presenter indexs and presenter and work contexts
		/// </summary>
		/// <param name="presenterContext">The context in which presenters are created</param>
		/// <param name="declaredViewPresenterIndex">The declared mappings between view types and presenters</param>
		/// <param name="workContext">The unit of work to be committed when this request is disposed</param>
		public MvpContext(
			IPresenterContext presenterContext,
			IViewPresenterIndex declaredViewPresenterIndex,
			IWorkContext workContext)
		{
			Contract.Requires(presenterContext != null);
			Contract.Requires(declaredViewPresenterIndex != null);
			Contract.Requires(workContext != null);

			_presenterContext = presenterContext;
			_declaredViewPresenterIndex = declaredViewPresenterIndex;
			_workContext = workContext;
		}

		#region IDisposable
		/// <summary>
		/// Disposes of this context, which commits the work context
		/// </summary>
		public void Dispose()
		{
			Dispose(true);

			GC.SuppressFinalize(this);
		}

		private void Dispose(bool disposing)
		{
			if(!_disposed)
			{
				if(disposing)
				{
					_workContext.Commit();
				}
			}

			_disposed = true;
		}
		#endregion

		#region IMvpContext
		/// <summary>
		/// Enrolls the specified view in this context using the specified presentation instructions
		/// </summary>
		/// <param name="view">The view to be presented</param>
		/// <param name="presentation">Instructions on how to present the view</param>
		public void Present(object view, ViewPresentation presentation)
		{
			lock(_presentationSync)
			{
				if(!presentation.ReplaceDeclaredPresenters)
				{
					Present(view, GetDeclaredPresenters(view));
				}

				Present(view, presentation.Presenters);
			}
		}
		#endregion

		private IEnumerable<IPresenterReference> GetDeclaredPresenters(object view)
		{
			return _declaredViewPresenterIndex.GetPresenters(view.GetType());
		}

		private void Present(object view, IEnumerable<IPresenterReference> presenters)
		{
			foreach(var presenter in presenters)
			{
				presenter.CreateIn(_presenterContext, view);
			}
		}
	}
}